// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id: Process.scala 33 2009-11-03 15:56:20Z normen.mueller $
package scaup.io

import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder }
import java.io.{ IOException, InputStream, OutputStream, BufferedReader, InputStreamReader, PrintWriter, File }
import java.util.concurrent.LinkedBlockingQueue

/** The <code>Process</code> object contains convenience functions for running external processes.
 * 
 *  <b>Note:</b> Initiated from <code>scala.tools.nsc.Process@18697</code> by Paul Phillips
 *
 *  @author   <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version  0.0.2 */
object Process {  
  lazy val runtime = Runtime.getRuntime()
  
  private [Process] class Builder(val pb: JProcessBuilder) {
    def this(cmd: String*) = this(new JProcessBuilder(cmd.toArray: _*))
    
    def start() = new Process(() => pb.start())

    def withOnlyEnv(env: Map[String, String]): this.type = {
      pb.environment.clear()
      withEnv(env)
    }

    def withEnv(env: Map[String,String]): this.type = {
      if (env != null) {
        val jmap = pb.environment()
        for ((k, v) <- env) jmap.put(k, v)
      }
      this
    }

    def withCwd(cwd: File): this.type = {
      if (cwd != null)
        pb directory cwd
      this 
    }
    
    def withRedirectedErrorStream(merged: Boolean): this.type = {
      pb redirectErrorStream merged
      this
    }

    override def toString() = "ProcessBuilder(%s)" format pb.command()
  }
  
  private val shell: String => Array[String] =
    if (OS.isWindows)
      Array("cmd.exe", "/C", _)
    else Array("sh", "-c", _)
  
  type Env = Map[String, String]
  
  def apply(cmd: String): Process = apply(cmd, null, null, false)
  def apply(cmd: String, env: Env): Process = apply(cmd, env, null, false)
  def apply(cmd: String, cwd: File): Process = apply(cmd, null, cwd, false)
  def apply(cmd: String, env: Env, cwd: File): Process = apply(cmd, env, cwd, false)
  def apply(cmd: String, env: Env, cwd: File, r: Boolean) = exec(shell(cmd), env, cwd, r)
  
  
  /** Executes the given command line.
   *
   * @param  cmd the command line
   * @param  env the environment to use
   * @param  cwd the working directory to set
   * @param  r   flag for output redirection
   * @return a Process object*/
  def exec(cmd: Seq[String], env: Env, cwd: File, r: Boolean) = new Builder(cmd: _*) withEnv env withCwd cwd start  
}
import Process._

class Process private [io] (processCreator: () => JProcess) extends Iterable[String] {  
  lazy val process = processCreator()
  
  def exitValue(): Option[Int] = try { Some(process.exitValue()) } catch { case e: IllegalThreadStateException => None }
    
  def waitFor() = process.waitFor()
  def destroy() = process.destroy()
  def rerun() = new Process(processCreator)
    
  def stdout    = elements
  def elements  = _out.elements
  def stderr    = _err.elements
  lazy val stdin = new PrintWriter(_in, true)
  
  class StreamedConsumer(in: InputStream) extends Thread with Iterable[String] {
    private val queue = new LinkedBlockingQueue[String]
    private val reader = new BufferedReader(new InputStreamReader(in))

    def elements = {
      join()  // make sure this thread is complete
      new Iterator[String] {
        val it = queue.iterator()
        def hasNext = it.hasNext
        def next = it.next
      }
    }
    override def run() {
      reader.readLine match { 
        case null =>
        case x    =>
          queue put x
          run()
      }
    }
  }
  
  private val _err = createConsumer(process.getErrorStream)
  private val _out = createConsumer(process.getInputStream)
  private val _in  = process.getOutputStream()
  
  private def createConsumer(in: InputStream) = {
    val t = new StreamedConsumer(in)
    t.start()
    t
  }
  
  override def toString() = "Process(%s)" format process.toString()  
}
